"""
03_nodes_and_edges.py - Nodes and Edges: The Building Blocks
=============================================================
Chapter 5: Functions, patterns, and parallel execution.

KEY CONCEPTS:
  ★ Node = Python function(state) -> dict
  ★ Normal Edge = Always follows to next node
  ★ Parallel Edges = Multiple edges from one node
  ★ Annotated[list, add] = Append reducer for tracking

Run: python src/03_nodes_and_edges.py
"""
from typing import TypedDict, Annotated
from operator import add
from langgraph.graph import StateGraph, START, END

class ProcessingState(TypedDict):
    raw_text: str
    steps_completed: Annotated[list, add]
    word_count: int
    cleaned_text: str
    sentiment: str

def clean_text(state: ProcessingState) -> dict:
    text = state["raw_text"].strip().lower()
    text = " ".join(text.split())
    return {"cleaned_text": text, "steps_completed": ["clean_text"]}

def analyze_sentiment(state: ProcessingState) -> dict:
    text = state.get("cleaned_text", "")
    positive = ["good", "great", "excellent", "happy", "love", "amazing"]
    negative = ["bad", "terrible", "awful", "hate", "angry", "worst"]
    pos = sum(1 for w in positive if w in text)
    neg = sum(1 for w in negative if w in text)
    sentiment = "positive" if pos > neg else "negative" if neg > pos else "neutral"
    return {"sentiment": sentiment, "steps_completed": ["analyze_sentiment"]}

def count_words(state: ProcessingState) -> dict:
    count = len(state.get("cleaned_text", "").split())
    return {"word_count": count, "steps_completed": ["count_words"]}

graph = StateGraph(ProcessingState)
graph.add_node("clean", clean_text)
graph.add_node("sentiment", analyze_sentiment)
graph.add_node("count", count_words)
graph.add_edge(START, "clean")
graph.add_edge("clean", "sentiment")
graph.add_edge("sentiment", "count")
graph.add_edge("count", END)
app = graph.compile()

if __name__ == "__main__":
    print("=" * 60)
    print("03: Nodes and Edges")
    print("=" * 60)
    tests = [
        "This product is absolutely AMAZING and I love it!",
        "Terrible experience. The worst service I've ever had.",
        "The package arrived on Tuesday as expected.",
    ]
    for text in tests:
        result = app.invoke({"raw_text": text})
        print(f"\n  Input: {text[:50]}...")
        print(f"  Sentiment: {result['sentiment']}")
        print(f"  Words: {result['word_count']}")
        print(f"  Steps: {result['steps_completed']}")
    print(f"\n{'=' * 60}\n✅ Nodes and edges complete!")
